#include "tiffiop.h"

#if HAVE_IEEEFP
#define TIFFCvtNativeToIEEEFloat(tif, n, fp)
#define TIFFCvtNativeToIEEEDouble(tif, n, dp)
#else
extern  void TIFFCvtNativeToIEEEFloat( TIFF*, uint32, float* );
extern  void TIFFCvtNativeToIEEEDouble( TIFF*, uint32, double* );
#endif

static  int TIFFWriteNormalTag( TIFF*, TIFFDirEntry*, const TIFFFieldInfo* );
static  void TIFFSetupShortLong( TIFF*, ttag_t, TIFFDirEntry*, uint32 );
static  void TIFFSetupShort( TIFF*, ttag_t, TIFFDirEntry*, uint16 );
static  int TIFFSetupShortPair( TIFF*, ttag_t, TIFFDirEntry* );
static  int TIFFWritePerSampleShorts( TIFF*, ttag_t, TIFFDirEntry* );
static  int TIFFWritePerSampleAnys( TIFF*, TIFFDataType, ttag_t, TIFFDirEntry* );
static  int TIFFWriteShortTable( TIFF*, ttag_t, TIFFDirEntry*, uint32, uint16** );
static  int TIFFWriteShortArray( TIFF*, TIFFDataType, ttag_t, TIFFDirEntry*, uint32, uint16* );
static  int TIFFWriteLongArray( TIFF *, TIFFDataType, ttag_t, TIFFDirEntry*, uint32, uint32* );
static  int TIFFWriteRationalArray( TIFF *, TIFFDataType, ttag_t, TIFFDirEntry*, uint32, float* );
static  int TIFFWriteFloatArray( TIFF *, TIFFDataType, ttag_t, TIFFDirEntry*, uint32, float* );
static  int TIFFWriteDoubleArray( TIFF *, TIFFDataType, ttag_t, TIFFDirEntry*, uint32, double* );
static  int TIFFWriteByteArray( TIFF*, TIFFDirEntry*, char* );
static  int TIFFWriteAnyArray( TIFF*, TIFFDataType, ttag_t, TIFFDirEntry*, uint32, double* );
#ifdef COLORIMETRY_SUPPORT
static  int TIFFWriteTransferFunction( TIFF*, TIFFDirEntry* );
#endif
#ifdef CMYK_SUPPORT
static  int TIFFWriteInkNames( TIFF*, TIFFDirEntry* );
#endif
static  int TIFFWriteData( TIFF*, TIFFDirEntry*, char* );
static  int TIFFLinkDirectory( TIFF* );

#define WriteRationalPair(type, tag1, v1, tag2, v2) {   \
    if (!TIFFWriteRational(tif, type, tag1, dir, v1)) \
      goto bad;         \
    if (!TIFFWriteRational(tif, type, tag2, dir+1, v2)) \
      goto bad;         \
    dir++;              \
  }
#define TIFFWriteRational(tif, type, tag, dir, v) \
  TIFFWriteRationalArray((tif), (type), (tag), (dir), 1, &(v))
#ifndef TIFFWriteRational
static  int TIFFWriteRational( TIFF*, TIFFDataType, ttag_t, TIFFDirEntry*, float );
#endif

static int _TIFFWriteDirectory( TIFF* tif, int done ) {
  uint16 dircount;
  toff_t diroff;
  ttag_t tag;
  uint32 nfields;
  tsize_t dirsize;
  char* data;
  TIFFDirEntry* dir;
  TIFFDirectory* td;
  u_long b, fields[FIELD_SETLONGS];
  int fi, nfi;
  if( tif->tif_mode == O_RDONLY ) {
    return ( 1 );
  }
  if( done ) {
    if( tif->tif_flags & TIFF_POSTENCODE ) {
      tif->tif_flags &= ~TIFF_POSTENCODE;
      if( !( *tif->tif_postencode )( tif ) ) {
        TIFFError( tif->tif_name,
                   "Error post-encoding before directory write" );
        return ( 0 );
      }
    }
    ( *tif->tif_close )( tif );
    if( tif->tif_rawcc > 0 && !TIFFFlushData1( tif ) ) {
      TIFFError( tif->tif_name,
                 "Error flushing data before directory write" );
      return ( 0 );
    }
    if( ( tif->tif_flags & TIFF_MYBUFFER ) && tif->tif_rawdata ) {
      _TIFFfree( tif->tif_rawdata );
      tif->tif_rawdata = NULL;
      tif->tif_rawcc = 0;
      tif->tif_rawdatasize = 0;
    }
    tif->tif_flags &= ~( TIFF_BEENWRITING | TIFF_BUFFERSETUP );
  }
  td = &tif->tif_dir;
  nfields = 0;
  for( b = 0; b <= FIELD_LAST; b++ )
    if( TIFFFieldSet( tif, b ) && b != FIELD_CUSTOM ) {
      nfields += ( b < FIELD_SUBFILETYPE ? 2 : 1 );
    }
  nfields += td->td_customValueCount;
  dirsize = nfields * sizeof( TIFFDirEntry );
  data = ( char* ) _TIFFmalloc( dirsize );
  if( data == NULL ) {
    TIFFError( tif->tif_name,
               "Cannot write directory, out of space" );
    return ( 0 );
  }
  if( tif->tif_diroff == 0 && !TIFFLinkDirectory( tif ) ) {
    goto bad;
  }
  tif->tif_dataoff = ( toff_t )(
                       tif->tif_diroff + sizeof( uint16 ) + dirsize + sizeof( toff_t ) );
  if( tif->tif_dataoff & 1 ) {
    tif->tif_dataoff++;
  }
  ( void ) TIFFSeekFile( tif, tif->tif_dataoff, SEEK_SET );
  tif->tif_curdir++;
  dir = ( TIFFDirEntry* ) data;
  _TIFFmemcpy( fields, td->td_fieldsset, sizeof( fields ) );
  if( FieldSet( fields, FIELD_EXTRASAMPLES ) && !td->td_extrasamples ) {
    ResetFieldBit( fields, FIELD_EXTRASAMPLES );
    nfields--;
    dirsize -= sizeof( TIFFDirEntry );
  }               /*XXX*/
  for( fi = 0, nfi = tif->tif_nfields; nfi > 0; nfi--, fi++ ) {
    const TIFFFieldInfo* fip = tif->tif_fieldinfo[fi];
    if( fip->field_bit == FIELD_CUSTOM ) {
      int ci, is_set = FALSE;
      for( ci = 0; ci < td->td_customValueCount; ci++ ) {
        is_set |= ( td->td_customValues[ci].info == fip );
      }
      if( !is_set ) {
        continue;
      }
    } else if( !FieldSet( fields, fip->field_bit ) ) {
      continue;
    }
    switch( fip->field_bit ) {
      case FIELD_STRIPOFFSETS:
        tag = isTiled( tif ) ?
              TIFFTAG_TILEOFFSETS : TIFFTAG_STRIPOFFSETS;
        if( tag != fip->field_tag ) {
          continue;
        }
        if( !TIFFWriteLongArray( tif, TIFF_LONG, tag, dir,
                                 ( uint32 ) td->td_nstrips, td->td_stripoffset ) ) {
          goto bad;
        }
        break;
      case FIELD_STRIPBYTECOUNTS:
        tag = isTiled( tif ) ?
              TIFFTAG_TILEBYTECOUNTS : TIFFTAG_STRIPBYTECOUNTS;
        if( tag != fip->field_tag ) {
          continue;
        }
        if( !TIFFWriteLongArray( tif, TIFF_LONG, tag, dir,
                                 ( uint32 ) td->td_nstrips, td->td_stripbytecount ) ) {
          goto bad;
        }
        break;
      case FIELD_ROWSPERSTRIP:
        TIFFSetupShortLong( tif, TIFFTAG_ROWSPERSTRIP,
                            dir, td->td_rowsperstrip );
        break;
      case FIELD_COLORMAP:
        if( !TIFFWriteShortTable( tif, TIFFTAG_COLORMAP, dir,
                                  3, td->td_colormap ) ) {
          goto bad;
        }
        break;
      case FIELD_IMAGEDIMENSIONS:
        TIFFSetupShortLong( tif, TIFFTAG_IMAGEWIDTH,
                            dir++, td->td_imagewidth );
        TIFFSetupShortLong( tif, TIFFTAG_IMAGELENGTH,
                            dir, td->td_imagelength );
        break;
      case FIELD_TILEDIMENSIONS:
        TIFFSetupShortLong( tif, TIFFTAG_TILEWIDTH,
                            dir++, td->td_tilewidth );
        TIFFSetupShortLong( tif, TIFFTAG_TILELENGTH,
                            dir, td->td_tilelength );
        break;
      case FIELD_COMPRESSION:
        TIFFSetupShort( tif, TIFFTAG_COMPRESSION,
                        dir, td->td_compression );
        break;
      case FIELD_PHOTOMETRIC:
        TIFFSetupShort( tif, TIFFTAG_PHOTOMETRIC,
                        dir, td->td_photometric );
        break;
      case FIELD_POSITION:
        WriteRationalPair( TIFF_RATIONAL,
                           TIFFTAG_XPOSITION, td->td_xposition,
                           TIFFTAG_YPOSITION, td->td_yposition );
        break;
      case FIELD_RESOLUTION:
        WriteRationalPair( TIFF_RATIONAL,
                           TIFFTAG_XRESOLUTION, td->td_xresolution,
                           TIFFTAG_YRESOLUTION, td->td_yresolution );
        break;
      case FIELD_BITSPERSAMPLE:
      case FIELD_MINSAMPLEVALUE:
      case FIELD_MAXSAMPLEVALUE:
      case FIELD_SAMPLEFORMAT:
        if( !TIFFWritePerSampleShorts( tif, fip->field_tag, dir ) ) {
          goto bad;
        }
        break;
      case FIELD_SMINSAMPLEVALUE:
      case FIELD_SMAXSAMPLEVALUE:
        if( !TIFFWritePerSampleAnys( tif,
                                     _TIFFSampleToTagType( tif ), fip->field_tag, dir ) ) {
          goto bad;
        }
        break;
      case FIELD_PAGENUMBER:
      case FIELD_HALFTONEHINTS:
        #ifdef YCBCR_SUPPORT
      case FIELD_YCBCRSUBSAMPLING:
        #endif
        #ifdef CMYK_SUPPORT
      case FIELD_DOTRANGE:
        #endif
        if( !TIFFSetupShortPair( tif, fip->field_tag, dir ) ) {
          goto bad;
        }
        break;
        #ifdef CMYK_SUPPORT
      case FIELD_INKNAMES:
        if( !TIFFWriteInkNames( tif, dir ) ) {
          goto bad;
        }
        break;
        #endif
        #ifdef COLORIMETRY_SUPPORT
      case FIELD_TRANSFERFUNCTION:
        if( !TIFFWriteTransferFunction( tif, dir ) ) {
          goto bad;
        }
        break;
        #endif
        #if SUBIFD_SUPPORT
      case FIELD_SUBIFD:
        if( !TIFFWriteNormalTag( tif, dir, fip ) ) {
          goto bad;
        }
        if( dir->tdir_count > 0 ) {
          tif->tif_flags |= TIFF_INSUBIFD;
          tif->tif_nsubifd = ( uint16 ) dir->tdir_count;
          if( dir->tdir_count > 1 ) {
            tif->tif_subifdoff = dir->tdir_offset;
          } else
          { tif->tif_subifdoff = ( uint32 )( tif->tif_diroff + sizeof( uint16 ) + ( ( char* )&dir->tdir_offset - data ) ); }
        }
        break;
        #endif
      default:
        if( !TIFFWriteNormalTag( tif, dir, fip ) ) {
          goto bad;
        }
        break;
    }
    dir++;
    if( fip->field_bit != FIELD_CUSTOM ) {
      ResetFieldBit( fields, fip->field_bit );
    }
  }
  dircount = ( uint16 ) nfields;
  diroff = ( uint32 ) tif->tif_nextdiroff;
  if( tif->tif_flags & TIFF_SWAB ) {
    for( dir = ( TIFFDirEntry* ) data; dircount; dir++, dircount-- ) {
      TIFFSwabArrayOfShort( &dir->tdir_tag, 2 );
      TIFFSwabArrayOfLong( &dir->tdir_count, 2 );
    }
    dircount = ( uint16 ) nfields;
    TIFFSwabShort( &dircount );
    TIFFSwabLong( &diroff );
  }
  ( void ) TIFFSeekFile( tif, tif->tif_diroff, SEEK_SET );
  if( !WriteOK( tif, &dircount, sizeof( dircount ) ) ) {
    TIFFError( tif->tif_name, "Error writing directory count" );
    goto bad;
  }
  if( !WriteOK( tif, data, dirsize ) ) {
    TIFFError( tif->tif_name, "Error writing directory contents" );
    goto bad;
  }
  if( !WriteOK( tif, &diroff, sizeof( diroff ) ) ) {
    TIFFError( tif->tif_name, "Error writing directory link" );
    goto bad;
  }
  if( done ) {
    TIFFFreeDirectory( tif );
    tif->tif_flags &= ~TIFF_DIRTYDIRECT;
    ( *tif->tif_cleanup )( tif );
    TIFFCreateDirectory( tif );
  }
  _TIFFfree( data );
  return ( 1 );
bad:
  _TIFFfree( data );
  return ( 0 );
}
#undef WriteRationalPair

int
TIFFWriteDirectory( TIFF* tif ) {
  return _TIFFWriteDirectory( tif, TRUE );
}

int TIFFCheckpointDirectory( TIFF* tif ) {
  int rc;
  if( tif->tif_dir.td_stripoffset == NULL ) {
    ( void ) TIFFSetupStrips( tif );
  }
  rc = _TIFFWriteDirectory( tif, FALSE );
  ( void ) TIFFSetWriteOffset( tif, TIFFSeekFile( tif, 0, SEEK_END ) );
  return rc;
}

static int TIFFWriteNormalTag( TIFF* tif, TIFFDirEntry* dir, const TIFFFieldInfo* fip ) {
  u_short wc = ( u_short ) fip->field_writecount;
  uint32 wc2;
  dir->tdir_tag = ( uint16 ) fip->field_tag;
  dir->tdir_type = ( u_short ) fip->field_type;
  dir->tdir_count = wc;
#define WRITEF(x,y) x(tif, fip->field_type, fip->field_tag, dir, wc, y)
  switch( fip->field_type ) {
    case TIFF_SHORT:
    case TIFF_SSHORT:
      if( wc > 1 ) {
        uint16* wp;
        if( wc == ( u_short ) TIFF_VARIABLE
            || fip->field_passcount ) {
          TIFFGetField( tif, fip->field_tag, &wc, &wp );
        } else
        { TIFFGetField( tif, fip->field_tag, &wp ); }
        if( !WRITEF( TIFFWriteShortArray, wp ) ) {
          return ( 0 );
        }
      } else {
        if( fip->field_passcount ) {
          uint16* wp;
          TIFFGetField( tif, fip->field_tag, &wc, &wp );
          if( !WRITEF( TIFFWriteShortArray, wp ) ) {
            return 0;
          }
        } else {
          uint16 sv;
          TIFFGetField( tif, fip->field_tag, &sv );
          dir->tdir_offset =
            TIFFInsertData( tif, dir->tdir_type, sv );
        }
      }
      break;
    case TIFF_LONG:
    case TIFF_SLONG:
    case TIFF_IFD:
      if( wc > 1 ) {
        uint32* lp;
        if( wc == ( u_short ) TIFF_VARIABLE
            || fip->field_passcount ) {
          TIFFGetField( tif, fip->field_tag, &wc, &lp );
        } else
        { TIFFGetField( tif, fip->field_tag, &lp ); }
        if( !WRITEF( TIFFWriteLongArray, lp ) ) {
          return ( 0 );
        }
      } else {
        if( fip->field_passcount ) {
          uint32* lp;
          TIFFGetField( tif, fip->field_tag, &wc, &lp );
          if( !WRITEF( TIFFWriteLongArray, lp ) ) {
            return 0;
          }
        } else {
          TIFFGetField( tif, fip->field_tag, &dir->tdir_offset );
        }
      }
      break;
    case TIFF_RATIONAL:
    case TIFF_SRATIONAL:
      if( wc > 1 ) {
        float* fp;
        if( wc == ( u_short ) TIFF_VARIABLE
            || fip->field_passcount ) {
          TIFFGetField( tif, fip->field_tag, &wc, &fp );
        } else
        { TIFFGetField( tif, fip->field_tag, &fp ); }
        if( !WRITEF( TIFFWriteRationalArray, fp ) ) {
          return ( 0 );
        }
      } else {
        if( fip->field_passcount ) {
          float* fp;
          TIFFGetField( tif, fip->field_tag, &wc, &fp );
          if( !WRITEF( TIFFWriteRationalArray, fp ) ) {
            return 0;
          }
        } else {
          float fv;
          TIFFGetField( tif, fip->field_tag, &fv );
          if( !WRITEF( TIFFWriteRationalArray, &fv ) ) {
            return ( 0 );
          }
        }
      }
      break;
    case TIFF_FLOAT:
      if( wc > 1 ) {
        float* fp;
        if( wc == ( u_short ) TIFF_VARIABLE
            || fip->field_passcount ) {
          TIFFGetField( tif, fip->field_tag, &wc, &fp );
        } else {
          TIFFGetField( tif, fip->field_tag, &fp );
        }
        if( !WRITEF( TIFFWriteFloatArray, fp ) ) {
          return ( 0 );
        }
      } else {
        if( fip->field_passcount ) {
          float* fp;
          TIFFGetField( tif, fip->field_tag, &wc, &fp );
          if( !WRITEF( TIFFWriteFloatArray, fp ) ) {
            return 0;
          }
        } else {
          float fv;
          TIFFGetField( tif, fip->field_tag, &fv );
          if( !WRITEF( TIFFWriteFloatArray, &fv ) ) {
            return ( 0 );
          }
        }
      }
      break;
    case TIFF_DOUBLE:
      if( wc > 1 ) {
        double* dp;
        if( wc == ( u_short ) TIFF_VARIABLE
            || fip->field_passcount ) {
          TIFFGetField( tif, fip->field_tag, &wc, &dp );
        } else
        { TIFFGetField( tif, fip->field_tag, &dp ); }
        if( !WRITEF( TIFFWriteDoubleArray, dp ) ) {
          return ( 0 );
        }
      } else {
        if( fip->field_passcount ) {
          double* dp;
          TIFFGetField( tif, fip->field_tag, &wc, &dp );
          if( !WRITEF( TIFFWriteDoubleArray, dp ) ) {
            return 0;
          }
        } else {
          double dv;
          TIFFGetField( tif, fip->field_tag, &dv );
          if( !WRITEF( TIFFWriteDoubleArray, &dv ) ) {
            return ( 0 );
          }
        }
      }
      break;
    case TIFF_ASCII: {
      char* cp;
      TIFFGetField( tif, fip->field_tag, &cp );
      dir->tdir_count = ( uint32 )( strlen( cp ) + 1 );
      if( !TIFFWriteByteArray( tif, dir, cp ) ) {
        return ( 0 );
      }
    }
    break;
    case TIFF_BYTE:
    case TIFF_SBYTE:
      if( wc > 1 ) {
        char* cp;
        if( wc == ( u_short ) TIFF_VARIABLE
            || fip->field_passcount ) {
          TIFFGetField( tif, fip->field_tag, &wc, &cp );
          dir->tdir_count = wc;
        } else if( wc == ( u_short ) TIFF_VARIABLE2 ) {
          TIFFGetField( tif, fip->field_tag, &wc2, &cp );
          dir->tdir_count = wc2;
        } else
        { TIFFGetField( tif, fip->field_tag, &cp ); }
        if( !TIFFWriteByteArray( tif, dir, cp ) ) {
          return ( 0 );
        }
      } else {
        if( fip->field_passcount ) {
          char* cp;
          TIFFGetField( tif, fip->field_tag, &wc, &cp );
          dir->tdir_count = wc;
          if( !TIFFWriteByteArray( tif, dir, cp ) ) {
            return 0;
          }
        } else {
          char cv;
          TIFFGetField( tif, fip->field_tag, &cv );
          if( !TIFFWriteByteArray( tif, dir, &cv ) ) {
            return ( 0 );
          }
        }
      }
      break;
    case TIFF_UNDEFINED: {
      char* cp;
      if( wc == ( u_short ) TIFF_VARIABLE ) {
        TIFFGetField( tif, fip->field_tag, &wc, &cp );
        dir->tdir_count = wc;
      } else if( wc == ( u_short ) TIFF_VARIABLE2 ) {
        TIFFGetField( tif, fip->field_tag, &wc2, &cp );
        dir->tdir_count = wc2;
      } else {
        TIFFGetField( tif, fip->field_tag, &cp );
      }
      if( !TIFFWriteByteArray( tif, dir, cp ) ) {
        return ( 0 );
      }
    }
    break;
    case TIFF_NOTYPE:
      break;
  }
  return ( 1 );
}
#undef WRITEF

static void TIFFSetupShortLong( TIFF* tif, ttag_t tag, TIFFDirEntry* dir, uint32 v ) {
  dir->tdir_tag = ( uint16 ) tag;
  dir->tdir_count = 1;
  if( v > 0xffffL ) {
    dir->tdir_type = ( short ) TIFF_LONG;
    dir->tdir_offset = v;
  } else {
    dir->tdir_type = ( short ) TIFF_SHORT;
    dir->tdir_offset = TIFFInsertData( tif, ( int ) TIFF_SHORT, v );
  }
}

static void TIFFSetupShort( TIFF* tif, ttag_t tag, TIFFDirEntry* dir, uint16 v ) {
  dir->tdir_tag = ( uint16 ) tag;
  dir->tdir_count = 1;
  dir->tdir_type = ( short ) TIFF_SHORT;
  dir->tdir_offset = TIFFInsertData( tif, ( int ) TIFF_SHORT, v );
}
#undef MakeShortDirent

#ifndef TIFFWriteRational

static int TIFFWriteRational( TIFF* tif, TIFFDataType type, ttag_t tag, TIFFDirEntry* dir, float v ) {
  return ( TIFFWriteRationalArray( tif, type, tag, dir, 1, &v ) );
}
#endif

#define NITEMS(x) (sizeof (x) / sizeof (x[0]))

static int TIFFWritePerSampleShorts( TIFF* tif, ttag_t tag, TIFFDirEntry* dir ) {
  uint16 buf[10], v;
  uint16* w = buf;
  int i, status, samples = tif->tif_dir.td_samplesperpixel;
  if( samples > NITEMS( buf ) ) {
    w = ( uint16* ) _TIFFmalloc( samples * sizeof( uint16 ) );
    if( w == NULL ) {
      TIFFError( tif->tif_name,
                 "No space to write per-sample shorts" );
      return ( 0 );
    }
  }
  TIFFGetField( tif, tag, &v );
  for( i = 0; i < samples; i++ ) {
    w[i] = v;
  }
  status = TIFFWriteShortArray( tif, TIFF_SHORT, tag, dir, samples, w );
  if( w != buf ) {
    _TIFFfree( ( char* ) w );
  }
  return ( status );
}

static int TIFFWritePerSampleAnys( TIFF* tif, TIFFDataType type, ttag_t tag, TIFFDirEntry* dir ) {
  double buf[10], v;
  double* w = buf;
  int i, status;
  int samples = ( int ) tif->tif_dir.td_samplesperpixel;
  if( samples > NITEMS( buf ) ) {
    w = ( double* ) _TIFFmalloc( samples * sizeof( double ) );
    if( w == NULL ) {
      TIFFError( tif->tif_name,
                 "No space to write per-sample values" );
      return ( 0 );
    }
  }
  TIFFGetField( tif, tag, &v );
  for( i = 0; i < samples; i++ ) {
    w[i] = v;
  }
  status = TIFFWriteAnyArray( tif, type, tag, dir, samples, w );
  if( w != buf ) {
    _TIFFfree( w );
  }
  return ( status );
}
#undef NITEMS

static int TIFFSetupShortPair( TIFF* tif, ttag_t tag, TIFFDirEntry* dir ) {
  uint16 v[2];
  TIFFGetField( tif, tag, &v[0], &v[1] );
  return ( TIFFWriteShortArray( tif, TIFF_SHORT, tag, dir, 2, v ) );
}

static int TIFFWriteShortTable( TIFF* tif, ttag_t tag, TIFFDirEntry* dir, uint32 n, uint16** table ) {
  uint32 i, off;
  dir->tdir_tag = ( uint16 ) tag;
  dir->tdir_type = ( short ) TIFF_SHORT;
  dir->tdir_count = ( uint32 )( 1L << tif->tif_dir.td_bitspersample );
  off = tif->tif_dataoff;
  for( i = 0; i < n; i++ )
    if( !TIFFWriteData( tif, dir, ( char * )table[i] ) ) {
      return ( 0 );
    }
  dir->tdir_count *= n;
  dir->tdir_offset = off;
  return ( 1 );
}

static int TIFFWriteByteArray( TIFF* tif, TIFFDirEntry* dir, char* cp ) {
  if( dir->tdir_count > 4 ) {
    if( !TIFFWriteData( tif, dir, cp ) ) {
      return ( 0 );
    }
  } else
  { _TIFFmemcpy( &dir->tdir_offset, cp, dir->tdir_count ); }
  return ( 1 );
}

static int TIFFWriteShortArray( TIFF* tif, TIFFDataType type, ttag_t tag, TIFFDirEntry* dir, uint32 n, uint16* v ) {
  dir->tdir_tag = ( uint16 ) tag;
  dir->tdir_type = ( short ) type;
  dir->tdir_count = n;
  if( n <= 2 ) {
    if( tif->tif_header.tiff_magic == TIFF_BIGENDIAN ) {
      dir->tdir_offset = ( uint32 )( ( long ) v[0] << 16 );
      if( n == 2 ) {
        dir->tdir_offset |= v[1] & 0xffff;
      }
    } else {
      dir->tdir_offset = v[0] & 0xffff;
      if( n == 2 ) {
        dir->tdir_offset |= ( long ) v[1] << 16;
      }
    }
    return ( 1 );
  } else
  { return ( TIFFWriteData( tif, dir, ( char* ) v ) ); }
}

static int TIFFWriteLongArray( TIFF* tif, TIFFDataType type, ttag_t tag, TIFFDirEntry* dir, uint32 n, uint32* v ) {
  dir->tdir_tag = ( uint16 ) tag;
  dir->tdir_type = ( short ) type;
  dir->tdir_count = n;
  if( n == 1 ) {
    dir->tdir_offset = v[0];
    return ( 1 );
  } else {
    return ( TIFFWriteData( tif, dir, ( char* ) v ) );
  }
}

static int TIFFWriteRationalArray( TIFF* tif, TIFFDataType type, ttag_t tag, TIFFDirEntry* dir, uint32 n, float* v ) {
  uint32 i;
  uint32* t;
  int status;
  dir->tdir_tag = ( uint16 ) tag;
  dir->tdir_type = ( short ) type;
  dir->tdir_count = n;
  t = ( uint32* ) _TIFFmalloc( 2 * n * sizeof( uint32 ) );
  if( t == NULL ) {
    TIFFError( tif->tif_name,
               "No space to write RATIONAL array" );
    return ( 0 );
  }
  for( i = 0; i < n; i++ ) {
    float fv = v[i];
    int sign = 1;
    uint32 den;
    if( fv < 0 ) {
      if( type == TIFF_RATIONAL ) {
        TIFFWarning( tif->tif_name,
                     "\"%s\": Information lost writing value (%g) as (unsigned) RATIONAL",
                     _TIFFFieldWithTag( tif, tag )->field_name, fv );
        fv = 0;
      } else
      { fv = -fv, sign = -1; }
    }
    den = 1L;
    if( fv > 0 ) {
      while( fv < 1L << ( 31 - 3 ) && den < 1L << ( 31 - 3 ) ) {
        fv *= 1 << 3, den *= 1L << 3;
      }
    }
    t[2 * i + 0] = ( uint32 )( sign * ( fv + 0.5 ) );
    t[2 * i + 1] = den;
  }
  status = TIFFWriteData( tif, dir, ( char * )t );
  _TIFFfree( ( char* ) t );
  return ( status );
}

static int TIFFWriteFloatArray( TIFF* tif, TIFFDataType type, ttag_t tag, TIFFDirEntry* dir, uint32 n, float* v ) {
  dir->tdir_tag = ( uint16 ) tag;
  dir->tdir_type = ( short ) type;
  dir->tdir_count = n;
  TIFFCvtNativeToIEEEFloat( tif, n, v );
  if( n == 1 ) {
    dir->tdir_offset = *( uint32* ) &v[0];
    return ( 1 );
  } else {
    return ( TIFFWriteData( tif, dir, ( char* ) v ) );
  }
}

static int
TIFFWriteDoubleArray( TIFF* tif, TIFFDataType type, ttag_t tag, TIFFDirEntry* dir, uint32 n, double* v ) {
  dir->tdir_tag = ( uint16 ) tag;
  dir->tdir_type = ( short ) type;
  dir->tdir_count = n;
  TIFFCvtNativeToIEEEDouble( tif, n, v );
  return ( TIFFWriteData( tif, dir, ( char* ) v ) );
}

static int TIFFWriteAnyArray( TIFF* tif, TIFFDataType type, ttag_t tag, TIFFDirEntry* dir, uint32 n, double* v ) {
  char buf[10 * sizeof( double )];
  char* w = buf;
  int i, status = 0;
  if( n * TIFFDataWidth( type ) > sizeof buf ) {
    w = ( char* ) _TIFFmalloc( n * TIFFDataWidth( type ) );
    if( w == NULL ) {
      TIFFError( tif->tif_name, "No space to write array" );
      return ( 0 );
    }
  }
  switch( type ) {
    case TIFF_BYTE: {
      uint8* bp = ( uint8* ) w;
      for( i = 0; i < ( int ) n; i++ ) {
        bp[i] = ( uint8 ) v[i];
      }
      dir->tdir_tag = ( uint16 ) tag;
      dir->tdir_type = ( short ) type;
      dir->tdir_count = n;
      if( !TIFFWriteByteArray( tif, dir, ( char* ) bp ) ) {
        goto out;
      }
    }
    break;
    case TIFF_SBYTE: {
      int8* bp = ( int8* ) w;
      for( i = 0; i < ( int ) n; i++ ) {
        bp[i] = ( int8 ) v[i];
      }
      dir->tdir_tag = ( uint16 ) tag;
      dir->tdir_type = ( short ) type;
      dir->tdir_count = n;
      if( !TIFFWriteByteArray( tif, dir, ( char* ) bp ) ) {
        goto out;
      }
    }
    break;
    case TIFF_SHORT: {
      uint16* bp = ( uint16* ) w;
      for( i = 0; i < ( int ) n; i++ ) {
        bp[i] = ( uint16 ) v[i];
      }
      if( !TIFFWriteShortArray( tif, type, tag, dir, n, ( uint16* )bp ) ) {
        goto out;
      }
    }
    break;
    case TIFF_SSHORT: {
      int16* bp = ( int16* ) w;
      for( i = 0; i < ( int ) n; i++ ) {
        bp[i] = ( int16 ) v[i];
      }
      if( !TIFFWriteShortArray( tif, type, tag, dir, n, ( uint16* )bp ) ) {
        goto out;
      }
    }
    break;
    case TIFF_LONG: {
      uint32* bp = ( uint32* ) w;
      for( i = 0; i < ( int ) n; i++ ) {
        bp[i] = ( uint32 ) v[i];
      }
      if( !TIFFWriteLongArray( tif, type, tag, dir, n, bp ) ) {
        goto out;
      }
    }
    break;
    case TIFF_SLONG: {
      int32* bp = ( int32* ) w;
      for( i = 0; i < ( int ) n; i++ ) {
        bp[i] = ( int32 ) v[i];
      }
      if( !TIFFWriteLongArray( tif, type, tag, dir, n, ( uint32* ) bp ) ) {
        goto out;
      }
    }
    break;
    case TIFF_FLOAT: {
      float* bp = ( float* ) w;
      for( i = 0; i < ( int ) n; i++ ) {
        bp[i] = ( float ) v[i];
      }
      if( !TIFFWriteFloatArray( tif, type, tag, dir, n, bp ) ) {
        goto out;
      }
    }
    break;
    case TIFF_DOUBLE:
      return ( TIFFWriteDoubleArray( tif, type, tag, dir, n, v ) );
    default:
      goto out;
  }
  status = 1;
out:
  if( w != buf ) {
    _TIFFfree( w );
  }
  return ( status );
}

#ifdef COLORIMETRY_SUPPORT
static int
TIFFWriteTransferFunction( TIFF* tif, TIFFDirEntry* dir ) {
  TIFFDirectory* td = &tif->tif_dir;
  tsize_t n = ( 1L << td->td_bitspersample ) * sizeof( uint16 );
  uint16** tf = td->td_transferfunction;
  int ncols;
  switch( td->td_samplesperpixel - td->td_extrasamples ) {
    default:
      if( _TIFFmemcmp( tf[0], tf[2], n ) ) {
        ncols = 3;
        break;
      }
    case 2:
      if( _TIFFmemcmp( tf[0], tf[1], n ) ) {
        ncols = 3;
        break;
      }
    case 1:
    case 0:
      ncols = 1;
  }
  return ( TIFFWriteShortTable( tif, TIFFTAG_TRANSFERFUNCTION, dir, ncols, tf ) );
}
#endif

#ifdef CMYK_SUPPORT
static int
TIFFWriteInkNames( TIFF* tif, TIFFDirEntry* dir ) {
  TIFFDirectory* td = &tif->tif_dir;
  dir->tdir_tag = TIFFTAG_INKNAMES;
  dir->tdir_type = ( short ) TIFF_ASCII;
  dir->tdir_count = td->td_inknameslen;
  return ( TIFFWriteByteArray( tif, dir, td->td_inknames ) );
}
#endif

static int TIFFWriteData( TIFF* tif, TIFFDirEntry* dir, char* cp ) {
  tsize_t cc;
  if( tif->tif_flags & TIFF_SWAB ) {
    switch( dir->tdir_type ) {
      case TIFF_SHORT:
      case TIFF_SSHORT:
        TIFFSwabArrayOfShort( ( uint16* ) cp, dir->tdir_count );
        break;
      case TIFF_LONG:
      case TIFF_SLONG:
      case TIFF_FLOAT:
        TIFFSwabArrayOfLong( ( uint32* ) cp, dir->tdir_count );
        break;
      case TIFF_RATIONAL:
      case TIFF_SRATIONAL:
        TIFFSwabArrayOfLong( ( uint32* ) cp, 2 * dir->tdir_count );
        break;
      case TIFF_DOUBLE:
        TIFFSwabArrayOfDouble( ( double* ) cp, dir->tdir_count );
        break;
    }
  }
  dir->tdir_offset = tif->tif_dataoff;
  cc = dir->tdir_count * TIFFDataWidth( ( TIFFDataType ) dir->tdir_type );
  if( SeekOK( tif, dir->tdir_offset ) &&
      WriteOK( tif, cp, cc ) ) {
    tif->tif_dataoff += ( cc + 1 ) & ~1;
    return ( 1 );
  }
  TIFFError( tif->tif_name, "Error writing data for field \"%s\"",
             _TIFFFieldWithTag( tif, dir->tdir_tag )->field_name );
  return ( 0 );
}

int TIFFRewriteDirectory( TIFF *tif ) {
  static const char module[] = "TIFFRewriteDirectory";
  if( tif->tif_diroff == 0 ) {
    return TIFFWriteDirectory( tif );
  }
  if( tif->tif_header.tiff_diroff == tif->tif_diroff ) {
    tif->tif_header.tiff_diroff = 0;
    tif->tif_diroff = 0;
    #if defined(__hpux) && defined(__LP64__)
#define HDROFF(f) ((toff_t)(unsigned long) &(((TIFFHeader*) 0)->f))
    #else
#define HDROFF(f) ((toff_t) &(((TIFFHeader*) 0)->f))
    #endif
    TIFFSeekFile( tif, HDROFF( tiff_diroff ), SEEK_SET );
    if( !WriteOK( tif, &( tif->tif_header.tiff_diroff ),
                  sizeof( tif->tif_diroff ) ) ) {
      TIFFError( tif->tif_name, "Error updating TIFF header" );
      return ( 0 );
    }
  } else {
    toff_t  nextdir, off;
    nextdir = tif->tif_header.tiff_diroff;
    do {
      uint16 dircount;
      if( !SeekOK( tif, nextdir ) ||
          !ReadOK( tif, &dircount, sizeof( dircount ) ) ) {
        TIFFError( module, "Error fetching directory count" );
        return ( 0 );
      }
      if( tif->tif_flags & TIFF_SWAB ) {
        TIFFSwabShort( &dircount );
      }
      ( void ) TIFFSeekFile( tif,
                             dircount * sizeof( TIFFDirEntry ), SEEK_CUR );
      if( !ReadOK( tif, &nextdir, sizeof( nextdir ) ) ) {
        TIFFError( module, "Error fetching directory link" );
        return ( 0 );
      }
      if( tif->tif_flags & TIFF_SWAB ) {
        TIFFSwabLong( &nextdir );
      }
    } while( nextdir != tif->tif_diroff && nextdir != 0 );
    off = TIFFSeekFile( tif, 0, SEEK_CUR ); /* get current offset */
    ( void ) TIFFSeekFile( tif, off - ( toff_t )sizeof( nextdir ), SEEK_SET );
    tif->tif_diroff = 0;
    if( !WriteOK( tif, &( tif->tif_diroff ), sizeof( nextdir ) ) ) {
      TIFFError( module, "Error writing directory link" );
      return ( 0 );
    }
  }
  /*
  ** Now use TIFFWriteDirectory() normally.
  */
  return TIFFWriteDirectory( tif );
}


/*
   Link the current directory into the
   directory chain for the file.
*/
static int
TIFFLinkDirectory( TIFF* tif ) {
  static const char module[] = "TIFFLinkDirectory";
  toff_t nextdir;
  toff_t diroff, off;
  tif->tif_diroff = ( TIFFSeekFile( tif, ( toff_t ) 0, SEEK_END ) + 1 ) & ~ 1;
  diroff = tif->tif_diroff;
  if( tif->tif_flags & TIFF_SWAB ) {
    TIFFSwabLong( &diroff );
  }
  #if SUBIFD_SUPPORT
  if( tif->tif_flags & TIFF_INSUBIFD ) {
    ( void ) TIFFSeekFile( tif, tif->tif_subifdoff, SEEK_SET );
    if( !WriteOK( tif, &diroff, sizeof( diroff ) ) ) {
      TIFFError( module,
                 "%s: Error writing SubIFD directory link",
                 tif->tif_name );
      return ( 0 );
    }
    /*
       Advance to the next SubIFD or, if this is
       the last one configured, revert back to the
       normal directory linkage.
    */
    if( --tif->tif_nsubifd ) {
      tif->tif_subifdoff += sizeof( diroff );
    } else
    { tif->tif_flags &= ~TIFF_INSUBIFD; }
    return ( 1 );
  }
  #endif
  if( tif->tif_header.tiff_diroff == 0 ) {
    /*
       First directory, overwrite offset in header.
    */
    tif->tif_header.tiff_diroff = tif->tif_diroff;
#define HDROFF(f) ((toff_t) &(((TIFFHeader*) 0)->f))
    ( void ) TIFFSeekFile( tif, HDROFF( tiff_diroff ), SEEK_SET );
    if( !WriteOK( tif, &diroff, sizeof( diroff ) ) ) {
      TIFFError( tif->tif_name, "Error writing TIFF header" );
      return ( 0 );
    }
    return ( 1 );
  }
  /*
     Not the first directory, search to the last and append.
  */
  nextdir = tif->tif_header.tiff_diroff;
  do {
    uint16 dircount;
    if( !SeekOK( tif, nextdir ) ||
        !ReadOK( tif, &dircount, sizeof( dircount ) ) ) {
      TIFFError( module, "Error fetching directory count" );
      return ( 0 );
    }
    if( tif->tif_flags & TIFF_SWAB ) {
      TIFFSwabShort( &dircount );
    }
    ( void ) TIFFSeekFile( tif,
                           dircount * sizeof( TIFFDirEntry ), SEEK_CUR );
    if( !ReadOK( tif, &nextdir, sizeof( nextdir ) ) ) {
      TIFFError( module, "Error fetching directory link" );
      return ( 0 );
    }
    if( tif->tif_flags & TIFF_SWAB ) {
      TIFFSwabLong( &nextdir );
    }
  } while( nextdir != 0 );
  off = TIFFSeekFile( tif, 0, SEEK_CUR ); /* get current offset */
  ( void ) TIFFSeekFile( tif, off - ( toff_t )sizeof( nextdir ), SEEK_SET );
  if( !WriteOK( tif, &diroff, sizeof( diroff ) ) ) {
    TIFFError( module, "Error writing directory link" );
    return ( 0 );
  }
  return ( 1 );
}
